﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using Imgur.Net.Auth;
using System.Net;
using System.IO;
using Imgur.Net.JSON;
using Imgur.Net.Util;
using Newtonsoft.Json;

namespace Imgur.Net
{
    /// <summary>
    /// Provides access to the image endpoint functionality of Imgur.
    /// </summary>
    /// <remarks>
    /// The image endpoint of Imgur contains the primary functionality of that service such as CRUD actions. Use of this class
    /// requires an authentication context to supply either anonymous or user owned credentials. The authentication requirement
    /// is due to the Imgur API requiring all requests to be be authenticated.
    /// </remarks>
    public class DefaultImageProvider : ImageProvider
    {
        private const string DefaultUriTemplate = "https://api.imgur.com/3/image";
        private readonly string baseUriTemplate;
        private ImgurAuthProvider imgurAuthProvider;

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <remarks>
        /// This constructor uses the default API endpoint at <code>https://api.imgur.com/3/image</code> for request calls. 
        /// To change this please use one of the overloaded constructors.
        /// </remarks>
        /// <param name="authProvider">Authorization provider to privde credentials to requests made from this class.</param>
        [ExcludeFromCodeCoverage]
        public DefaultImageProvider(ImgurAuthProvider authProvider) : this( authProvider, DefaultUriTemplate)
        {
            
        }

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <remarks>
        /// This constructor allows specification of the base URI template to be used for request calls. In the URI, <code>{0}</code> token
        /// will be replaced where necessary by the image ID.
        /// </remarks>
        /// <param name="authProvider">Authorization provider to privde credentials to requests made from this class.</param>
        /// <param name="baseUriTemplate">Base URI template to use for future requests.</param>
        public DefaultImageProvider(ImgurAuthProvider authProvider, string baseUriTemplate )
        {
            this.AuthenticationProvider = authProvider;
            this.baseUriTemplate = baseUriTemplate;
        }

        /// <summary>
        /// Returns an ImageInfo describing the image identified by the <c>id</c> parameter.
        /// </summary>
        /// <param name="id">Imgur image ID</param>
        /// <returns>ImageInfo instance containing properties of the image.</returns>
        /// <seealso cref="ImageInfo"/>
        public ImageInfo GetImageInfo(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentException("image ID is requred.");

            string infoRequestTemplate = baseUriTemplate + "/{0}";
            string requestUrl = String.Format(infoRequestTemplate, id);

            WebRequest hr = WebRequest.Create(requestUrl);
            AuthenticationProvider.GetAuthorizationContext().AddContextToRequest(ref hr);

            string responseString = string.Empty;

            using (WebResponse response = hr.GetResponse())
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader sReader = new StreamReader(responseStream))
                    {
                        responseString = sReader.ReadToEnd();
                    }
                }
            }

            JSONWrapper<ImageInfo> responseWrapper = JsonConvert.DeserializeObject<JSONWrapper<ImageInfo>>(responseString);

            return responseWrapper.Data;

        }

        /// <summary>
        /// Uploads the given image to Imgur service with the specified title and description ( if any ). This image
        /// will be a stand alone item and not be part of an album.
        /// </summary>
        /// <param name="image">The image to upload.</param>
        /// <param name="title">The title of the image.</param>
        /// <param name="description">A description of the image.</param>
        public ImageInfo UploadImage(System.Drawing.Image image, string title, string description)
        {
            byte[] binaryData = GetImageData(image);
            return UploadImage( binaryData, title, description);
        }

        /// <summary>
        /// Uploads the given image to Imgur service with the specified title and description ( if any ). This image
        /// will added to the album specified by the albumId parameter.
        /// </summary>
        /// <param name="image">The image to upload.</param>
        /// <param name="title">The title of the image.</param>
        /// <param name="description">A description of the image.</param>
        /// <param name="albumId">The ID of the album to which this image should be added.</param>
        public ImageInfo UploadImage(System.Drawing.Image image, string title, string description, string albumId)
        {
            byte[] binaryData = GetImageData(image);
            return UploadImage(binaryData, title, description, albumId);
        }

        /// <summary>
        /// Uploads the given image binary data to Imgur service with the specified title and description ( if any ). This image
        /// will be a stand alone item and not be part of an album.
        /// </summary>
        /// <param name="imageData">The image to upload.</param>
        /// <param name="title">The title of the image.</param>
        /// <param name="description">A description of the image.</param>
        public ImageInfo UploadImage(byte[] imageData, string title, string description)
        {
            return UploadImage(imageData, title, description, null);
        }

        /// <summary>
        /// Uploads the given image binary data to Imgur service with the specified title and description ( if any ). This image
        /// will added to the album specified by the albumId parameter.
        /// </summary>
        /// <param name="imageData">The image to upload.</param>
        /// <param name="title">The title of the image.</param>
        /// <param name="description">A description of the image.</param>
        /// <param name="albumId">The ID of the album to which this image should be added.</param>
        public ImageInfo UploadImage(byte[] imageData, string title, string description, string albumId)
        {
            if (imageData == null || imageData.Length == 0)
                throw new ArgumentException("Image data is required.");

            string requestUrl = baseUriTemplate;

            WebRequest hr = WebRequest.Create(requestUrl);
            AuthenticationProvider.GetAuthorizationContext().AddContextToRequest(ref hr);

            hr.Method = "POST";

            string boundary = "-------------------------------------" + DateTime.Now.Ticks;
            string newLine = Environment.NewLine;
            string kvPair = "--" + boundary + newLine +
                            "Content-Disposition: form-data; name=\"{0}\"" + newLine + newLine + "{1}" + newLine;

            hr.ContentType = "multipart/form-data; boundary=" + boundary;

            using (Stream s = hr.GetRequestStream())
            {
                using (StreamWriter sw = new StreamWriter(s))
                {

                    // Write request params    
                    if (! string.IsNullOrEmpty(albumId))
                    {
                        string val = string.Format(kvPair, "album", albumId);
                        sw.Write(val);
                    }

                    if (!string.IsNullOrEmpty(title))
                    {
                        string val = string.Format(kvPair, "title", title);
                        sw.Write(val);
                    }

                    if (!string.IsNullOrEmpty(description))
                    {
                        string val = string.Format(kvPair, "description", description);
                        sw.Write(val);
                    }

                    sw.Write(kvPair, "type", "base64");
                    sw.Write(kvPair, "image", Convert.ToBase64String(imageData));
                    sw.Write(boundary);
                    sw.Flush();
                    sw.Close();
                }
            }

            string responseString = string.Empty;

            try
            {
                using (WebResponse response = hr.GetResponse())
                {
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        using (StreamReader sReader = new StreamReader(responseStream))
                        {
                            responseString = sReader.ReadToEnd();
                        }
                    }
                }

            }
            catch (WebException wex)
            {
                responseString = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
            }
            JSONWrapper<ImageInfo> responseWrapper = JsonConvert.DeserializeObject<JSONWrapper<ImageInfo>>(responseString);

            return responseWrapper.Data;
        }

        private byte[] GetImageData(Image image)
        {
            byte[] data;

            using (MemoryStream ms = new MemoryStream())
            {
                //image.Save(ms, ImageFormat.Png);
                try
                {
                    image.Save(ms, image.RawFormat);
                }
                catch (Exception)
                {
                    image.Save(ms, ImageFormat.Png);
                }

                data = ms.ToArray();
            }

            return data;
        }

        /// <summary>
        /// Deleted the given Image ID from Imgur. For anonymous images the ID must be the deletehash of the image. For
        /// account owned images the simple Image ID is acceptable.
        /// </summary>
        /// <param name="id">ID of the image to delete. Deletehash for anonymouse images or ID for account owned images.</param>
        public bool? DeleteImage(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentException("image ID is requred.");

            string infoRequestTemplate = baseUriTemplate + "/{0}";
            string requestUrl = String.Format(infoRequestTemplate, id);

            WebRequest hr = WebRequest.Create(requestUrl);
            AuthenticationProvider.GetAuthorizationContext().AddContextToRequest(ref hr);
            hr.Method = "DELETE";

            string responseString = string.Empty;

            using (WebResponse response = hr.GetResponse())
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader sReader = new StreamReader(responseStream))
                    {
                        responseString = sReader.ReadToEnd();
                    }
                }
            }

            JSONWrapper<bool?> responseWrapper = JsonConvert.DeserializeObject<JSONWrapper<bool?>>(responseString);

            return responseWrapper.Data;
        }

        /// <summary>
        /// Updates the title and/or description of the given image. For anonymous images the id must be the deletehash of
        /// the image. For account owned images the Image ID is acceptable.
        /// </summary>
        /// <param name="id">ID of the image to update. Deletehash for anonymous images or ID for account owned images.</param>
        /// <param name="title">The new title for the image. Null to not update the title.</param>
        /// <param name="description">The new description for the image. Null to not update the image.</param>
        /// <returns>true if successful. False otherwise.</returns>
        public bool? UpdateImageInfo(string id, string title, string description)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");

            if (string.IsNullOrEmpty(title) && string.IsNullOrEmpty(description))
                throw new ArgumentException("title and description were both null or empty.");

            string infoRequestTemplate = baseUriTemplate + "/{0}";
            string requestUri = String.Format(infoRequestTemplate, id);

            Uri uri = new Uri(requestUri)
                .AddQuery("title", title)
                .AddQuery("description", description);

            WebRequest hr = WebRequest.Create(uri);

            AuthenticationProvider.GetAuthorizationContext().AddContextToRequest(ref hr);
            hr.Method = "PUT";

            string responseString = string.Empty;

            using (WebResponse response = hr.GetResponse())
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader sReader = new StreamReader(responseStream))
                    {
                        responseString = sReader.ReadToEnd();
                    }
                }
            }

            JSONWrapper<bool?> responseWrapper = JsonConvert.DeserializeObject<JSONWrapper<bool?>>(responseString);

            return responseWrapper.Data;
        }

        public ImgurAuthProvider AuthenticationProvider
        {
            get { return imgurAuthProvider; }
            set
            {
                imgurAuthProvider = value;
            }
        }
    }
}
